logo

1 Introduction

1.1 Set up

  • In RStudio go to File -> New Project -> Existing Directory -> Downloaded Github repository.
  • Run getwd() - the file path should end in “Introduction_to_GIS_in_R”.
  • Open a new script: File -> New File -> R Script.

1.2 Quick R basics

  • R is case sensitive - read_csv is not the same as read_CSV.
  • New objects are created using the <- notation, e.g new_object <- 2 * 5 .
  • To overwrite an object use <- and its current name, e.g. current_object <- st_union(current_object).
  • Function’s arguments have to be in () and they’re defined with a =, e.g. st_transform(x = lfb_sf, crs = 27700)
  • To see function’s documentation precede its name with a ?, e.g ?st_as_sf.

1.3 Install & load R libraries

If you have not installed the necessary packages run install.packages() and then load them using the library() function.

install.packages("sf", dependencies = TRUE, type = "win.binary")
install.packages("tmap", dependencies = TRUE, type = "win.binary")
install.packages("tidyverse", dependencies = TRUE, type = "win.binary")

library(sf) 
library(tmap)
library(tidyverse)

1.4 Aims

By the end of the course you will:

  • Know got to load spatial data into R using the sf library.
  • Be familiar with using GSS codes to join statistics to geographies.
  • Understand how spatial objects can be manipulated using tidyverse.
  • Understand how to use spatial joins.
  • Be aware of map projections and Coordinate Reference Systems (CRS) and be able to modify them.
  • Know how to make static and interactive maps in tmap.
  • Be able to export your maps and shapefiles.

1.5 GIS and R

R is commonly used for statistical analysis and programming, however it also has a range of geospatial libraries developed by a community of researchers and programmers. In the last few years, working with spatial data became much easier in R, with the development of the sf package. sf keeps all the spatial information for each observation in a geometry column which means that we can treat it like a normal data frame and also perform spatial operations on the data.

Simple feature collection with 5 features and 2 fields
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: 543417.3 ymin: 183674.3 xmax: 551943.8 ymax: 191137.3
projected CRS:  OSGB 1936 / British National Grid
     wd19cd         wd19nm                       geometry
1 E05000026          Abbey MULTIPOLYGON (((544338.3 18...
2 E05000027         Alibon MULTIPOLYGON (((549604.1 18...
3 E05000028      Becontree MULTIPOLYGON (((547563.4 18...
4 E05000029 Chadwell Heath MULTIPOLYGON (((548881 1910...
5 E05000030      Eastbrook MULTIPOLYGON (((551552.9 18...

2 Working with spatial data in R

2.1 London Fire Brigade Animal Rescue Data

Throughout this tutorial you will be using data from the London Fire Brigade - LFB Animal Rescue Data. It covers all incidents between 2009 and 2020 which included assistance to animals that may be trapped or in distress. The data is updated monthly and includes a range of variables for each incident including some location information (postcode, borough, ward),the date/time of the incidents, cost, and type of animal in trouble.

We want to visualise, and better understand how much money has been spent on animal related incidents between 2009 and 2020, and what the distribution is at the MSOA level of geography. To achieve this we will have to import spatial data, manipulate it, create summary statistics, and then plot it.

2.2 Loading spatial and non-spatial data

LFB data has been tidied up and saved as a Comma Separated Value file (.csv). We can use read_csv to open it in R.

2.2.1 Exercise - open LFB data

  • Create a new object called lfb by using read_csv(). Load data located in “data/csv/lfb_2009_2020.csv”.
  • Use glimpse() or head() to view lfb structure.

Solution

lfb <- read_csv("data/csv/lfb_2009_2020.csv")
head(lfb)

lfb is currently just a data frame - it has not got an explicit geometry column which links observations to their geographic location. It does however contain several columns which can be used to convert it into a spatial data format.

Ward_code column references the GSS codes of wards within which the observations fall. GSS codes can be used to join lfb data to boundaries from the Open Geography Portal. One issue with this particular column is that it does not indicate the currency of GSS codes. Wards are subject to frequent change, and as such it is best practice to be clear about the dates of any boundaries used by stating the exact code used, e.g. wd19cd. Because LFB data does not include this information we have no guarantee that the boundaries and GSS codes we join will match.

Fortunately we have also been provided with columns recording the easting, and northing of each incident. We can use those to convert lfb into an sf object. To achieve this we will use the st_as_sf() function which takes the following arguments:

new_object <- st_as_sf(x = input_data_frame, coords = c("x_coordinate_column", "y_coordinate_column"), crs = 27700)

2.2.2 Exercise - create spatial data

  • Create a new object called lfb_sf by converting lfb using the st_as_sf() function.
  • Use glimpse() or head() to view lfb_sf structure.

Solution

lfb_sf <- st_as_sf(x = lfb, coords = c("easting", "northing"), crs = 27700)
head(lfb_sf)
Simple feature collection with 6 features and 10 fields
geometry type:  POINT
dimension:      XY
bbox:           xmin: 504650 ymin: 164950 xmax: 554650 ymax: 192350
projected CRS:  OSGB 1936 / British National Grid

st_as_sf() converted the easting and northing columns to simple feature geometries and created a new column called geometry which holds spatial information for each row. Now that lfb is a spatial object we can plot it using the tmap package. For now we will use the qtm() function which creates a quick map, using tmap's default settings. qtm() only needs to be supplied with a simple feature object and is very useful for quickly inspecting your data. To quickly plot multiple layers on the same map use qtm() + qtm().

2.2.3 Exercise - quick static maps

  • Plot lfb_sf using the qtm() function.

Solution

qtm(lfb_sf)

We can also create interactive maps using the tmap, package by running tmap_mode("view") before executing qtm(). To reverse it and go back to static maps use tmap_mode("plot").

2.2.4 Exercise - quick interactive maps

  • Make an interactive map of lfb_sf using the qtm() function and tmap_mode("view").

Solution

tmap_mode("view")
qtm(lfb_sf)

2.3 Filtering by GSS code

It looks like some of the locations are located outside of London, however we are only interested in incidents within the Boroughs making up Greater London. To remove all points outside of London we will have to import a shapefile with Upper Tier Local Authorities (UTLA) and then use them to spatially filter lfb_sf data.

So far we have created our own sf objects by adding a geometry column. The UTLA data set is already a spatial one and as such we can use the st_read() function from the sf package to import it. st_read is extremely versatile and able to import most spatial data formats into R. The only argument that needs to be supplied to st_read is the full path to the UTLA boundaries

2.3.1 Exercise - loading shapefiles

  • Use st_read() to load the UTLA boundaries you downloaded at the beginning of the tutorial, asutla_2019.
  • UTLA path - data/shp/London_2019/London_and_surrounding_UTLAs.shp
  • Make a static map of the object you have just created using qtm() and setting tmap_mode("plot").

Solution

utla_2019 <- st_read("data/shp/London_2019/London_and_surrounding_UTLAs.shp", quiet = TRUE)
tmap_mode("plot")
qtm(utla_2019)

UTLA boundaries have loaded correctly but they currently cover London as well as some of the surrounding UTLAs. Because simple feature objects are data frames with a geometry column attached, any operations that we would perform on a normal data frame can also be performed on an object of class sf. Here we will use the dplyr::filter and stringr::str_detect() from the the tidyverse package to only keep UTLAs whose GSS code starts with “E09”. “E09” denotes that an entity is a London Borough.

2.3.2 Exercise - filter spatial data by variable

  • Inspect utla_2019 using head() or glimpse(), and identify which column holds the GSS codes - it should end in “cd”.
  • Create a new object called london_utla. Use dplyr::filter alongside stringr::str_detect() to only keep observations which have a GSS code starting with “E09”.
  • Plot london_utla to see if the results look correct.

Solution

head(utla_2019)
Simple feature collection with 6 features and 2 fields
geometry type:  POLYGON
dimension:      XY
bbox:           xmin: -1.140696 ymin: 50.91053 xmax: 1.451898 ymax: 52.09266
geographic CRS: WGS 84
  ctyua19cd       ctyua19nm                       geometry
1 E06000034        Thurrock POLYGON ((0.3919471 51.5657...
2 E06000039          Slough POLYGON ((-0.6239452 51.537...
3 E10000002 Buckinghamshire POLYGON ((-0.9501094 52.079...
4 E10000012           Essex POLYGON ((0.6834739 52.0869...
5 E10000015   Hertfordshire POLYGON ((-0.1573147 52.080...
6 E10000016            Kent POLYGON ((0.228006 51.47989...
london_utla <- filter(utla_2019, str_detect(ctyua19cd, "E09"))
qtm(london_utla)

Finally, for the next step, we only need the outer boundary of London - all the internal UTLA boundaries have to be removed and only the outer edges kept. sf has a function exactly for this purpose called st_union(). It only takes one argument, which is the sf object we want to merge.

2.3.3 Exercise - dissolve boundaries

  • Create a new object called london_boundary using the st_union function.
  • Plot it to check the results.

Solution

london_boundary <- st_union(london_utla)
qtm(london_boundary)

2.4 Spatial subsetting and CRS

In addition to subsetting by value, as we did with the UTLA boundaries earlier, we can also subset observations by evaluating their spatial relationship with another data set. We can for example select all UTLAs which are fully within Wales, every Output Area intersected by a river, or all households outside of city boundaries. There are a number of different spatial relationships which can be tested and used to subset observations.

sf has an inbuilt function called st_filter() which we can use to spatially subset observations. The function takes several arguments:

  • x - sf data frame we want to subset - lfb_sf
  • y - sf object used to evaluate the spatial relationship - london_boundary

Before running any spatial operations on two spatial objects it is always worth checking if their coordinate reference systems (CRS) match. sf will throw an error if that’s not the case. Try it for yourself below.

2.4.1 Exercise - spatial subset part 1

  • Use st_filter() to spatially subset lfb_sf by testing its relationship with london_boundary.

Solution:

lfb_sf <- st_filter(x = lfb_sf, y = london_boundary)

You should have got an error here saying x st_crs(x) == st_crs(y) is not TRUE. It means that objects x and y have different coordinate reference systems. Spatial operations require all objects to have the same CRS. We can see this for ourselves by running the st_crs() function, which returns the coordinate reference system of an object.

2.4.2 Exercise - check CRS

  • Run st_crs() on both and lfb_sf and london_boundary and compare the results.

Solution:

st_crs(lfb_sf)[[1]]
[1] "EPSG:27700"
st_crs(london_boundary)[[1]]
[1] "WGS 84"

st_crs() provides detailed information about the CRS and projection of data, but all we need to check is its first element denoted by [[1]]. We can see that lfb_sf uses EPSG:27700, while london_boundary is set to WGS 84. This problem can be solved by transforming london_boundary’s CRS to match that of lfb_sf, simply by using the correct EPSG code. To do so we will use the st_transform() function which takes two arguments:

  • x - sf object to be transformed
  • crs - EPSG code that we want to transform our data to - BNG is 27700.

2.4.3 Exercise - transform CRS

  • Run st_transform() to transform and overwrite london_boundary. Remember to set the correct CRS.
  • Run st_crs() on lfb_sf and newly transformed london_boundary and compare the results.

Solution:

london_boundary <- st_transform(london_boundary, crs = 27700)

st_crs(lfb_sf)[[1]]
[1] "EPSG:27700"
st_crs(london_boundary)[[1]]
[1] "EPSG:27700"

Now that the CRS are matching we should be able to spatially subset lfb_sf.

2.4.4 Exercise - spatial subset part 2

  • Use st_filter to spatially subset lfb_sf by testing its relationship with london_boundary. Overwrite lfb_sf with the subset data.
  • Plot it to check if the results are correct - all points should be within London.

Solution:

lfb_sf <- st_filter(x = lfb_sf, y = london_boundary)
 qtm(london_boundary) + qtm(lfb_sf)

2.5 Spatial and non-spatial joins

Simple features data can be joined to other data sets in two ways. We can either use a traditional, SQL like join, based on a value shared across the data sets or, since we have a geometry column, on the spatial relationship between the data sets. This is known as a spatial join, where variables from one data set are joined to another only on the basis of their spatial relationship. The most commonly used operation is known as a Point-in-Polygon join where data from a polygon is joined to the points within them.

In sf spatial joins are handled using the st_join(x, y) function with arguments:

  • x - sf object to which we are joining data (LHS in SQL)
  • y - sf object whose variables are being joined (RHS in SQL)

We will be joining the Middle Super Output Areas to LFB locations, which will then allow us to group and plot data at MSOA level. It’s important to note that we are using two different sets of MSOA boundaries - one to perform a spatial join, and another to visualise the data. Full Extent (BFE) boundaries are used for the former. This ensures that all points are correctly joined to MSOA boundaries, and that we don’t lose any data around bodies of water. To plot the data we use Super Generalised Clipped (BSC) boundaries. They are smaller in size, load faster, and, because they’re clipped to the coastline, appear in a way that’s familiar to most users.

2.5.1 Exercise - spatial joins

  • Read in BFE boundariesdata/shp/MSOA_2011_london/MSOA_2011_BFE_London.gpkg as msoa_london_BFE - use st_read()
  • Overwrite lfb_sf by running st_join() between lfb_sf and msoa_london_BFE
  • Inspect it using head() or glimpse() to see what columns have been added.
msoa_london_BFE <- st_read("data/shp/MSOA_2011_london/MSOA_2011_BFE_London.gpkg", quiet = TRUE)

lfb_sf <- st_join(lfb_sf, msoa_london_BFE) 

head(lfb_sf)
Simple feature collection with 6 features and 12 fields
geometry type:  POINT
dimension:      XY
bbox:           xmin: 504650 ymin: 164950 xmax: 554650 ymax: 192350
projected CRS:  OSGB 1936 / British National Grid

Now that MSOA11CD is attached to our observations we can create summary statistics for each MSOA. As mentioned before, we can use standard tidyverse functions on sf objects. Here, we will use dplyr to calculate the total number of incidents and their cost, and then use a non spatial join to attach those results to MSOA boundaries. At this stage we no longer need the geometry column for each LFB incident as a) we’re not performing any spatial operations on our points, and b) the geometry column can slow down/interrupt the dplyr::group_by function which we will be using. To remove the geometry column we use the st_drop_geometry() function directly in the dplyr pipe.

2.5.2 Exercise - MSOA summary statistics

  • Read in BSC boundariesdata/shp/MSOA_2011_london/MSOA_2011_BSC_London.shp as msoa_london_BSC - use st_read()
  • Use st_drop_geometry() on lfb_sf to remove geometry data.
  • Create summary statistics per MSOA - sum of cost_gbp as total_cost (use na.rm = TRUE), and the total number of incidents as n_cases. You will need to use group_by() and summarise()
  • Use mutate to create a new column called cost_per_incident which is equal to total_cost divided by n_cases.
  • Join lfb_msoa_stats to msoa_london_BSC, using left_join() and create a new object msoa_lfb
msoa_london_BSC <- st_read("data/shp/MSOA_2011_london/MSOA_2011_BSC_London.shp", quiet = TRUE) 

lfb_msoa_stats <- lfb_sf %>% 
                  st_drop_geometry() %>% 
                  group_by(MSOA11CD) %>% 
                  summarise(total_cost = sum(cost_gbp, na.rm=TRUE), n_cases = n()) %>% 
                  mutate(cost_per_incident = total_cost/n_cases)
                         
msoa_lfb <- left_join(msoa_london_BSC, lfb_msoa_stats)
head(msoa_lfb)
Simple feature collection with 6 features and 4 fields
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: 530967.8 ymin: 180492.9 xmax: 551943.8 ymax: 191097.3
projected CRS:  OSGB 1936 / British National Grid
   MSOA11CD total_cost n_cases cost_per_incident                       geometry
1 E02000001       4756      14          339.7143 MULTIPOLYGON (((532946.1 18...
2 E02000002       2221       7          317.2857 MULTIPOLYGON (((549000.7 19...
3 E02000003       4779      11          434.4545 MULTIPOLYGON (((548954.5 18...
4 E02000004       2917       7          416.7143 MULTIPOLYGON (((551943.8 18...
5 E02000005       1113       4          278.2500 MULTIPOLYGON (((549418.7 18...
6 E02000007       2466       8          308.2500 MULTIPOLYGON (((550392.7 18...

At this stage it is a good idea to save our data. We can do this using the st_write() function. It needs an sf object and the path and name of the output.

2.5.3 Exercise - save data to gpkg

  • Copy and execute the following code to save your data: st_write(msoa_lfb,"output/msoa_lfb.gpkg)

3 Making better maps

3.1 Introduction to tmap

Now that we have processed our data we can start mapping it. So far we have only used the qtm() function from the tmap package. This creates a default map and is great when all we want to do is quickly visualise our data. The full range of tmap functions gives us control over all elements of the final plot and allows us to create high quality maps.

tmap follows similar principles to ggplot2, where we first specify the data source - tm_shape, then the aesthetics of the plot - tm_polygons, tm_dots, etc., and then we make any final adjustments to the overall look - tm_layout. All functions need to be connected using the + symbol.

  • tm_shape() - sf object which you want to plot
  • tm_fill(), tm_borders(), tm_polygons(), tm_dots() - types of output
  • tm_layout() - controls layout of the map, titles, labels, etc.

tmap syntax: tm_shape(sf_object) + tm_borders(col = either "colour" or name of column which we want to plot) + tm_layout(main.title = "title of your map")

3.2 Guided exercise - mapping

Start by specifying which sf object is being mapped in tm_shape() and what column holds the values to be visualised. We will also change the legend’s title.

tm_shape(msoa_lfb) + 
  tm_polygons(col = "cost_per_incident", title = "Cost per Incident (£)")

Now let’s add london_boundary to have a thicker line around London.

tm_shape(msoa_lfb) + 
  tm_polygons(col = "cost_per_incident", title = "Cost per Incident (£)") + 
  tm_shape(london_boundary) + tm_borders(col = "black", lwd = 2)

Next we will add a scale bar and position it in the bottom left corner.

tm_shape(msoa_lfb) + 
  tm_polygons(col = "cost_per_incident", title = "Cost per Incident (£)") + 
  tm_shape(london_boundary) + tm_borders(col = "black", lwd = 2) +
  tm_scale_bar(position = c(0,0))

We can now remove the black frame from the map and add a title to our map.

tm_shape(msoa_lfb) + 
  tm_polygons(col = "cost_per_incident", title = "Cost per Incident (£)") + 
  tm_shape(london_boundary) + tm_borders(col = "black", lwd = 2) +
  tm_scale_bar(position = c(0,0)) +
   tm_layout(title = "Average cost of animal related incidents between 2009 and 2020",  
            frame = FALSE)

All of the map elements are now visible but they’re not in the right place. We can solve this by increasing the margins around our map. This will allow the title and the legend to move outwards.

tm_shape(msoa_lfb) + 
  tm_polygons(col = "cost_per_incident", title = "Cost per Incident (£)") + 
  tm_shape(london_boundary) + tm_borders(col = "black", lwd = 2) +
  tm_scale_bar(position = c(0,0)) +
   tm_layout(title = "Average cost of animal related incidents between 2009 and 2020",  
            frame = FALSE, inner.margins = c(0.1,0.1,0.1,0.15))

tmap breaks up numerical data into evenly sized categories by default, but you can provide it with custom breaks as well.

tm_shape(msoa_lfb) + 
  tm_polygons(col = "cost_per_incident", breaks = seq(0,1200,300), title = "Cost per Incident (£)") + 
  tm_shape(london_boundary) + tm_borders(col = "black", lwd = 2) +
  tm_scale_bar(position = c(0,0)) +
   tm_layout(title = "Average cost of animal related incidents between 2009 and 2020",  
            frame = FALSE, inner.margins = c(0.1,0.1,0.1,0.15))

You can also use other methods of automatic data categorisation. Jenks is a popular method for clustering data into classes. You can set the number of desired classes using the n argument, and the exact method with the style argument.

tm_shape(msoa_lfb) + 
  tm_polygons(col = "cost_per_incident", n = 4, style ="jenks", title = "Cost per Incident (£)") + 
  tm_shape(london_boundary) + tm_borders(col = "black", lwd = 2) +
  tm_scale_bar(position = c(0,0)) +
   tm_layout(title = "Average cost of animal related incidents between 2009 and 2020",  
            frame = FALSE, inner.margins = c(0.1,0.1,0.1,0.15))

We will use the manually set breaks to ensure consistent results. It’s also important to change the legend labels to ensure there are no overlapping values.

tm_shape(msoa_lfb) + 
  tm_polygons(col = "cost_per_incident",  breaks = seq(0,1200,300), title = "Cost per Incident (£)",
              labels = c("0 - 300", ">300 - 600", ">600 - 900", ">900 - 1200")) + 
  tm_shape(london_boundary) + tm_borders(col = "black", lwd = 2) +
  tm_scale_bar(position = c(0,0)) +
   tm_layout(title = "Average cost of animal related incidents between 2009 and 2020",  
            frame = FALSE, inner.margins = c(0.1,0.1,0.1,0.15))

Finally let’s change the colour of our map and increase the contrast. Choose a colour from R Colours.

tm_shape(msoa_lfb) + 
  tm_polygons(col = "cost_per_incident",  breaks = seq(0,1200,300), title = "Cost per Incident (£)",
              labels = c("  0 - 300", ">300 - 600", ">600 - 900", ">900 - 1200"), palette = "Blues", contrast = 1) + 
  tm_shape(london_boundary) + tm_borders(col = "black", lwd = 2) +
  tm_scale_bar(position = c(0,0)) +
   tm_layout(title = "Average cost of animal related incidents between 2009 and 2020",  
            frame = FALSE, inner.margins = c(0.1,0.1,0.1,0.15))

Finally, save your map as an R object and export it.

average_cost <- tm_shape(msoa_lfb) + 
  tm_polygons(col = "cost_per_incident",  breaks = seq(0,1200,300), title = "Cost per Incident (£)",
              labels = c("  0 - 300", ">300 - 600", ">600 - 900", ">900 - 1200"), palette = "Blues", contrast = 1) + 
  tm_shape(london_boundary) + tm_borders(col = "black", lwd = 2) +
  tm_scale_bar(position = c(0,0)) +
   tm_layout(title = "Average cost of animal related incidents between 2009 and 2020",  
            frame = FALSE, inner.margins = c(0.1,0.1,0.1,0.15))
tmap_save(average_cost, "output/maps/average_cost_msoa.png", width = 8, height = 5)

You can also view your choropleth as an interactive map. It helps to add an alpha argument to change your map’s transparency.

tmap_mode("view")
tmap mode set to interactive viewing
tm_shape(msoa_lfb) + 
  tm_polygons(col = "cost_per_incident", breaks = seq(0,1200,300), 
               labels = c("  0 - 300", ">300 - 600", ">600 - 900", ">900 - 1200"),
              title = "Cost per Incident (£)", palette = "Blues", contrast = 1, alpha = 0.8) + 
  tm_shape(london_boundary) + tm_borders(col = "black", lwd = 2) 
LS0tDQp0aXRsZTogIkludHJvZHVjdGlvbiB0byBHSVMgaW4gUiINCmRhdGU6ICJEZWNlbWJlciAyMDIwIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOg0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdGhlbWU6IGZsYXRseQ0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiAyDQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogJzInDQogICAgZGZfcHJpbnQ6IHBhZ2VkDQotLS0NCg0KYGBge3IsIGVjaG89RkFMU0V9DQpodG1sdG9vbHM6OmltZyhzcmMgPSBrbml0cjo6aW1hZ2VfdXJpKCJkYXRhL2ltZy9zbWFsbCBnZW8gaWNvbi5wbmciKSwgDQogICAgICAgICAgICAgICBhbHQgPSAnbG9nbycsIA0KICAgICAgICAgICAgICAgc3R5bGUgPSAncG9zaXRpb246YWJzb2x1dGU7IHRvcDo1MHB4OyByaWdodDowOyBwYWRkaW5nOjEwcHg7JykNCmBgYA0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChmaWcuYWxpZ249ImNlbnRlciIpDQpgYGANCg0KYGBge3IgbGlicmFyaWVzLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBlY2hvPUZBTFNFfQ0KbGlicmFyeSgic2YiKQ0KbGlicmFyeSgidGlkeXZlcnNlIikNCmxpYnJhcnkoInRtYXAiKQ0KbGlicmFyeSgia25pdHIiKQ0KYGBgDQoNCiMgSW50cm9kdWN0aW9uIA0KIyMgU2V0IHVwDQoNCiogSW4gUlN0dWRpbyBnbyB0byBGaWxlIC0+IE5ldyBQcm9qZWN0IC0+IEV4aXN0aW5nIERpcmVjdG9yeSAtPiBEb3dubG9hZGVkIEdpdGh1YiByZXBvc2l0b3J5Lg0KKiBSdW4gYGdldHdkKClgIC0gdGhlIGZpbGUgcGF0aCBzaG91bGQgZW5kIGluICJJbnRyb2R1Y3Rpb25fdG9fR0lTX2luX1IiLg0KKiBPcGVuIGEgbmV3IHNjcmlwdDogRmlsZSAtPiBOZXcgRmlsZSAtPiBSIFNjcmlwdC4NCg0KIyMgUXVpY2sgUiBiYXNpY3MNCg0KKiBSIGlzIGNhc2Ugc2Vuc2l0aXZlIC0gYHJlYWRfY3N2YCBpcyBub3QgdGhlIHNhbWUgYXMgYHJlYWRfQ1NWYC4NCiogTmV3IG9iamVjdHMgYXJlIGNyZWF0ZWQgdXNpbmcgdGhlIGA8LWAgbm90YXRpb24sIGUuZyBgbmV3X29iamVjdCA8LSAyICogNWAgLg0KKiBUbyBvdmVyd3JpdGUgYW4gb2JqZWN0IHVzZSBgPC1gIGFuZCBpdHMgY3VycmVudCBuYW1lLCBlLmcuIGBjdXJyZW50X29iamVjdCA8LSBzdF91bmlvbihjdXJyZW50X29iamVjdClgLg0KKiBGdW5jdGlvbidzIGFyZ3VtZW50cyBoYXZlIHRvIGJlIGluIGAoKWAgYW5kIHRoZXkncmUgZGVmaW5lZCB3aXRoIGEgYD1gLCBlLmcuIGBzdF90cmFuc2Zvcm0oeCA9IGxmYl9zZiwgY3JzID0gMjc3MDApYA0KKiBUbyBzZWUgZnVuY3Rpb24ncyBkb2N1bWVudGF0aW9uIHByZWNlZGUgaXRzIG5hbWUgd2l0aCBhIGA/YCwgZS5nIGA/c3RfYXNfc2ZgLg0KDQojIyBJbnN0YWxsICYgbG9hZCBSIGxpYnJhcmllcw0KDQpJZiB5b3UgaGF2ZSBub3QgaW5zdGFsbGVkIHRoZSBuZWNlc3NhcnkgcGFja2FnZXMgcnVuIGBpbnN0YWxsLnBhY2thZ2VzKClgIGFuZCB0aGVuIGxvYWQgdGhlbSB1c2luZyB0aGUgYGxpYnJhcnkoKWAgZnVuY3Rpb24uIA0KYGBge3IsIGV2YWw9RkFMU0V9DQppbnN0YWxsLnBhY2thZ2VzKCJzZiIsIGRlcGVuZGVuY2llcyA9IFRSVUUsIHR5cGUgPSAid2luLmJpbmFyeSIpDQppbnN0YWxsLnBhY2thZ2VzKCJ0bWFwIiwgZGVwZW5kZW5jaWVzID0gVFJVRSwgdHlwZSA9ICJ3aW4uYmluYXJ5IikNCmluc3RhbGwucGFja2FnZXMoInRpZHl2ZXJzZSIsIGRlcGVuZGVuY2llcyA9IFRSVUUsIHR5cGUgPSAid2luLmJpbmFyeSIpDQoNCmxpYnJhcnkoc2YpIA0KbGlicmFyeSh0bWFwKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpgYGANCg0KDQojIyBBaW1zDQoNCioqQnkgdGhlIGVuZCBvZiB0aGUgY291cnNlIHlvdSB3aWxsOiAqKg0KIA0KKiBLbm93IGdvdCB0byBsb2FkIHNwYXRpYWwgZGF0YSBpbnRvIFIgdXNpbmcgdGhlIGBzZmAgbGlicmFyeS4NCiogQmUgZmFtaWxpYXIgd2l0aCB1c2luZyBHU1MgY29kZXMgdG8gam9pbiBzdGF0aXN0aWNzIHRvIGdlb2dyYXBoaWVzLg0KKiBVbmRlcnN0YW5kIGhvdyBzcGF0aWFsIG9iamVjdHMgY2FuIGJlIG1hbmlwdWxhdGVkIHVzaW5nIGB0aWR5dmVyc2VgLg0KKiBVbmRlcnN0YW5kIGhvdyB0byB1c2Ugc3BhdGlhbCBqb2lucy4gDQoqIEJlIGF3YXJlIG9mIG1hcCBwcm9qZWN0aW9ucyBhbmQgQ29vcmRpbmF0ZSBSZWZlcmVuY2UgU3lzdGVtcyAoQ1JTKSBhbmQgYmUgYWJsZSB0byBtb2RpZnkgdGhlbS4NCiogS25vdyBob3cgdG8gbWFrZSBzdGF0aWMgYW5kIGludGVyYWN0aXZlIG1hcHMgaW4gYHRtYXBgLg0KKiBCZSBhYmxlIHRvIGV4cG9ydCB5b3VyIG1hcHMgYW5kIHNoYXBlZmlsZXMuDQoNCg0KIyMgR0lTIGFuZCBSDQoNClIgaXMgY29tbW9ubHkgdXNlZCBmb3Igc3RhdGlzdGljYWwgYW5hbHlzaXMgYW5kIHByb2dyYW1taW5nLCBob3dldmVyIGl0IGFsc28gaGFzIGEgcmFuZ2Ugb2YgZ2Vvc3BhdGlhbCBsaWJyYXJpZXMgZGV2ZWxvcGVkIGJ5IGEgY29tbXVuaXR5IG9mIHJlc2VhcmNoZXJzIGFuZCBwcm9ncmFtbWVycy4gSW4gdGhlIGxhc3QgZmV3IHllYXJzLCB3b3JraW5nIHdpdGggc3BhdGlhbCBkYXRhIGJlY2FtZSBtdWNoIGVhc2llciBpbiBSLCB3aXRoIHRoZSBkZXZlbG9wbWVudCBvZiB0aGUgYHNmYCBwYWNrYWdlLiBgc2ZgIGtlZXBzIGFsbCB0aGUgc3BhdGlhbCBpbmZvcm1hdGlvbiBmb3IgZWFjaCBvYnNlcnZhdGlvbiBpbiBhIGdlb21ldHJ5IGNvbHVtbiB3aGljaCBtZWFucyB0aGF0IHdlIGNhbiB0cmVhdCBpdCBsaWtlIGEgbm9ybWFsIGRhdGEgZnJhbWUgYW5kIGFsc28gcGVyZm9ybSBzcGF0aWFsIG9wZXJhdGlvbnMgb24gdGhlIGRhdGEuICANCg0KYGBge3IgZWNobz1GQUxTRX0NCndkXzIwMTlfYmdjIDwtIHN0X3JlYWQoImRhdGEvc2hwL1dhcmRzX0RlY2VtYmVyXzIwMTlfQm91bmRhcmllc19FV19CR0MvV2FyZHNfRGVjZW1iZXJfMjAxOV9Cb3VuZGFyaWVzX0VXX0JHQy5zaHAiLCBxdWlldCA9IFRSVUUpICU+JSANCiAgc2VsZWN0KHdkMTljZCwgd2QxOW5tKQ0KaGVhZCh3ZF8yMDE5X2JnYykNCmBgYA0KDQojIFdvcmtpbmcgd2l0aCBzcGF0aWFsIGRhdGEgaW4gUg0KIyMgTG9uZG9uIEZpcmUgQnJpZ2FkZSBBbmltYWwgUmVzY3VlIERhdGENCg0KVGhyb3VnaG91dCB0aGlzIHR1dG9yaWFsIHlvdSB3aWxsIGJlIHVzaW5nIGRhdGEgZnJvbSB0aGUgTG9uZG9uIEZpcmUgQnJpZ2FkZSAtIFtMRkIgQW5pbWFsIFJlc2N1ZSBEYXRhXShodHRwczovL2RhdGEubG9uZG9uLmdvdi51ay9kYXRhc2V0L2FuaW1hbC1yZXNjdWUtaW5jaWRlbnRzLWF0dGVuZGVkLWJ5LWxmYikuIEl0IGNvdmVycyBhbGwgaW5jaWRlbnRzIGJldHdlZW4gMjAwOSBhbmQgMjAyMCB3aGljaCBpbmNsdWRlZCBhc3Npc3RhbmNlIHRvIGFuaW1hbHMgdGhhdCBtYXkgYmUgdHJhcHBlZCBvciBpbiBkaXN0cmVzcy4gVGhlIGRhdGEgaXMgdXBkYXRlZCBtb250aGx5IGFuZCBpbmNsdWRlcyBhIHJhbmdlIG9mIHZhcmlhYmxlcyBmb3IgZWFjaCBpbmNpZGVudCBpbmNsdWRpbmcgc29tZSBsb2NhdGlvbiBpbmZvcm1hdGlvbiAocG9zdGNvZGUsIGJvcm91Z2gsIHdhcmQpLHRoZSBkYXRlL3RpbWUgb2YgdGhlIGluY2lkZW50cywgY29zdCwgYW5kIHR5cGUgb2YgYW5pbWFsIGluIHRyb3VibGUuIA0KDQpXZSB3YW50IHRvIHZpc3VhbGlzZSwgYW5kIGJldHRlciB1bmRlcnN0YW5kIGhvdyBtdWNoIG1vbmV5IGhhcyBiZWVuIHNwZW50IG9uIGFuaW1hbCByZWxhdGVkIGluY2lkZW50cyBiZXR3ZWVuIDIwMDkgYW5kIDIwMjAsIGFuZCB3aGF0IHRoZSBkaXN0cmlidXRpb24gaXMgYXQgdGhlIE1TT0EgbGV2ZWwgb2YgZ2VvZ3JhcGh5LiBUbyBhY2hpZXZlIHRoaXMgd2Ugd2lsbCBoYXZlIHRvIGltcG9ydCBzcGF0aWFsIGRhdGEsIG1hbmlwdWxhdGUgaXQsIGNyZWF0ZSBzdW1tYXJ5IHN0YXRpc3RpY3MsIGFuZCB0aGVuIHBsb3QgaXQuDQoNCiMjIExvYWRpbmcgc3BhdGlhbCBhbmQgbm9uLXNwYXRpYWwgZGF0YQ0KDQpMRkIgZGF0YSBoYXMgYmVlbiB0aWRpZWQgdXAgYW5kIHNhdmVkIGFzIGEgQ29tbWEgU2VwYXJhdGVkIFZhbHVlIGZpbGUgKC5jc3YpLiBXZSBjYW4gdXNlIGByZWFkX2NzdmAgdG8gb3BlbiBpdCBpbiBSLg0KDQojIyMgRXhlcmNpc2UgLSBvcGVuIExGQiBkYXRhDQoNCiogQ3JlYXRlIGEgbmV3IG9iamVjdCBjYWxsZWQgYGxmYmAgYnkgdXNpbmcgYHJlYWRfY3N2KClgLiBMb2FkIGRhdGEgbG9jYXRlZCBpbiAiZGF0YS9jc3YvbGZiXzIwMDlfMjAyMC5jc3YiLg0KKiBVc2UgYGdsaW1wc2UoKWAgb3IgYGhlYWQoKWAgdG8gdmlldyBgbGZiYCBzdHJ1Y3R1cmUuDQoNCioqU29sdXRpb24qKg0KYGBge3IgbWVzc2FnZT1GQUxTRX0NCmxmYiA8LSByZWFkX2NzdigiZGF0YS9jc3YvbGZiXzIwMDlfMjAyMC5jc3YiKQ0KaGVhZChsZmIpDQpgYGANCg0KYGxmYmAgaXMgY3VycmVudGx5IGp1c3QgYSBkYXRhIGZyYW1lIC0gaXQgaGFzIG5vdCBnb3QgYW4gZXhwbGljaXQgZ2VvbWV0cnkgY29sdW1uIHdoaWNoIGxpbmtzIG9ic2VydmF0aW9ucyB0byB0aGVpciBnZW9ncmFwaGljIGxvY2F0aW9uLiBJdCBkb2VzIGhvd2V2ZXIgY29udGFpbiBzZXZlcmFsIGNvbHVtbnMgd2hpY2ggY2FuIGJlIHVzZWQgdG8gY29udmVydCBpdCBpbnRvIGEgc3BhdGlhbCBkYXRhIGZvcm1hdC4gICANCg0KKipXYXJkX2NvZGUqKiBjb2x1bW4gcmVmZXJlbmNlcyB0aGUgR1NTIGNvZGVzIG9mIHdhcmRzIHdpdGhpbiB3aGljaCB0aGUgb2JzZXJ2YXRpb25zIGZhbGwuIEdTUyBjb2RlcyBjYW4gYmUgdXNlZCB0byBqb2luIGBsZmJgIGRhdGEgdG8gYm91bmRhcmllcyBmcm9tIHRoZSBPcGVuIEdlb2dyYXBoeSBQb3J0YWwuIE9uZSBpc3N1ZSB3aXRoIHRoaXMgcGFydGljdWxhciBjb2x1bW4gaXMgdGhhdCBpdCBkb2VzIG5vdCBpbmRpY2F0ZSB0aGUgY3VycmVuY3kgb2YgR1NTIGNvZGVzLiBXYXJkcyBhcmUgc3ViamVjdCB0byBmcmVxdWVudCBjaGFuZ2UsIGFuZCBhcyBzdWNoIGl0IGlzIGJlc3QgcHJhY3RpY2UgdG8gYmUgY2xlYXIgYWJvdXQgdGhlIGRhdGVzIG9mIGFueSBib3VuZGFyaWVzIHVzZWQgYnkgc3RhdGluZyB0aGUgZXhhY3QgY29kZSB1c2VkLCBlLmcuICoqd2QxOWNkKiouIEJlY2F1c2UgTEZCIGRhdGEgZG9lcyBub3QgaW5jbHVkZSB0aGlzIGluZm9ybWF0aW9uIHdlIGhhdmUgbm8gZ3VhcmFudGVlIHRoYXQgdGhlIGJvdW5kYXJpZXMgYW5kIEdTUyBjb2RlcyB3ZSBqb2luIHdpbGwgbWF0Y2guICANCg0KRm9ydHVuYXRlbHkgd2UgaGF2ZSBhbHNvIGJlZW4gcHJvdmlkZWQgd2l0aCBjb2x1bW5zIHJlY29yZGluZyB0aGUgKiplYXN0aW5nKiosIGFuZCAqKm5vcnRoaW5nKiogb2YgZWFjaCBpbmNpZGVudC4gV2UgY2FuIHVzZSB0aG9zZSB0byBjb252ZXJ0IGBsZmJgIGludG8gYW4gYHNmYCBvYmplY3QuIFRvIGFjaGlldmUgdGhpcyB3ZSB3aWxsIHVzZSB0aGUgYHN0X2FzX3NmKClgIGZ1bmN0aW9uIHdoaWNoIHRha2VzIHRoZSBmb2xsb3dpbmcgYXJndW1lbnRzOg0KDQpgbmV3X29iamVjdCA8LSBzdF9hc19zZih4ID0gaW5wdXRfZGF0YV9mcmFtZSwgY29vcmRzID0gYygieF9jb29yZGluYXRlX2NvbHVtbiIsICJ5X2Nvb3JkaW5hdGVfY29sdW1uIiksIGNycyA9IDI3NzAwKWANCg0KIyMjIEV4ZXJjaXNlIC0gY3JlYXRlIHNwYXRpYWwgZGF0YQ0KDQoqIENyZWF0ZSBhIG5ldyBvYmplY3QgY2FsbGVkIGBsZmJfc2ZgIGJ5IGNvbnZlcnRpbmcgYGxmYmAgdXNpbmcgdGhlIGBzdF9hc19zZigpYCBmdW5jdGlvbi4NCiogVXNlIGBnbGltcHNlKClgIG9yIGBoZWFkKClgIHRvIHZpZXcgYGxmYl9zZmAgc3RydWN0dXJlLg0KDQoqKlNvbHV0aW9uKioNCmBgYHtyfQ0KbGZiX3NmIDwtIHN0X2FzX3NmKHggPSBsZmIsIGNvb3JkcyA9IGMoImVhc3RpbmciLCAibm9ydGhpbmciKSwgY3JzID0gMjc3MDApDQpoZWFkKGxmYl9zZikNCmBgYA0KDQpgc3RfYXNfc2YoKWAgY29udmVydGVkIHRoZSAqKmVhc3RpbmcqKiBhbmQgKipub3J0aGluZyoqIGNvbHVtbnMgdG8gc2ltcGxlIGZlYXR1cmUgZ2VvbWV0cmllcyBhbmQgY3JlYXRlZCBhIG5ldyBjb2x1bW4gY2FsbGVkICoqZ2VvbWV0cnkqKiB3aGljaCBob2xkcyBzcGF0aWFsIGluZm9ybWF0aW9uIGZvciBlYWNoIHJvdy4gDQpOb3cgdGhhdCBgbGZiYCBpcyBhIHNwYXRpYWwgb2JqZWN0IHdlIGNhbiBwbG90IGl0IHVzaW5nIHRoZSBgdG1hcGAgcGFja2FnZS4gRm9yIG5vdyB3ZSB3aWxsIHVzZSB0aGUgYHF0bSgpYCBmdW5jdGlvbiB3aGljaCBjcmVhdGVzIGEgcXVpY2sgbWFwLCB1c2luZyBgdG1hcCdzYCBkZWZhdWx0IHNldHRpbmdzLiBgcXRtKClgIG9ubHkgbmVlZHMgdG8gYmUgc3VwcGxpZWQgd2l0aCBhIHNpbXBsZSBmZWF0dXJlIG9iamVjdCBhbmQgaXMgdmVyeSB1c2VmdWwgZm9yIHF1aWNrbHkgaW5zcGVjdGluZyB5b3VyIGRhdGEuIFRvIHF1aWNrbHkgcGxvdCBtdWx0aXBsZSBsYXllcnMgb24gdGhlIHNhbWUgbWFwIHVzZSBgcXRtKCkgKyBxdG0oKWAuIA0KDQojIyMgRXhlcmNpc2UgLSBxdWljayBzdGF0aWMgbWFwcw0KDQoqIFBsb3QgbGZiX3NmIHVzaW5nIHRoZSBgcXRtKClgIGZ1bmN0aW9uLg0KDQoqKlNvbHV0aW9uKioNCmBgYHtyfQ0KcXRtKGxmYl9zZikNCmBgYA0KDQoNCldlIGNhbiBhbHNvIGNyZWF0ZSBpbnRlcmFjdGl2ZSBtYXBzIHVzaW5nIHRoZSBgdG1hcGAsIHBhY2thZ2UgYnkgcnVubmluZyBgdG1hcF9tb2RlKCJ2aWV3IilgIGJlZm9yZSBleGVjdXRpbmcgYHF0bSgpYC4gDQpUbyByZXZlcnNlIGl0IGFuZCBnbyBiYWNrIHRvIHN0YXRpYyBtYXBzIHVzZSBgdG1hcF9tb2RlKCJwbG90IilgLg0KDQojIyMgRXhlcmNpc2UgLSBxdWljayBpbnRlcmFjdGl2ZSBtYXBzDQoNCiogTWFrZSBhbiBpbnRlcmFjdGl2ZSBtYXAgb2YgYGxmYl9zZmAgdXNpbmcgdGhlIGBxdG0oKWAgZnVuY3Rpb24gYW5kIGB0bWFwX21vZGUoInZpZXciKWAuDQoNCioqU29sdXRpb24qKg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQp0bWFwX21vZGUoInZpZXciKQ0KcXRtKGxmYl9zZikNCmBgYA0KDQojIyBGaWx0ZXJpbmcgYnkgR1NTIGNvZGUNCg0KSXQgbG9va3MgbGlrZSBzb21lIG9mIHRoZSBsb2NhdGlvbnMgYXJlIGxvY2F0ZWQgb3V0c2lkZSBvZiBMb25kb24sIGhvd2V2ZXIgd2UgYXJlIG9ubHkgaW50ZXJlc3RlZCBpbiBpbmNpZGVudHMgd2l0aGluIHRoZSBCb3JvdWdocyBtYWtpbmcgdXAgR3JlYXRlciBMb25kb24uIFRvIHJlbW92ZSBhbGwgcG9pbnRzIG91dHNpZGUgb2YgTG9uZG9uIHdlIHdpbGwgaGF2ZSB0byBpbXBvcnQgYSBzaGFwZWZpbGUgd2l0aCBVcHBlciBUaWVyIExvY2FsIEF1dGhvcml0aWVzIChVVExBKSBhbmQgdGhlbiB1c2UgdGhlbSB0byBzcGF0aWFsbHkgZmlsdGVyIGBsZmJfc2ZgIGRhdGEuIA0KDQpTbyBmYXIgd2UgaGF2ZSBjcmVhdGVkIG91ciBvd24gYHNmYCBvYmplY3RzIGJ5IGFkZGluZyBhIGdlb21ldHJ5IGNvbHVtbi4gVGhlIFVUTEEgZGF0YSBzZXQgaXMgYWxyZWFkeSBhIHNwYXRpYWwgb25lIGFuZCBhcyBzdWNoIHdlIGNhbiB1c2UgdGhlIGBzdF9yZWFkKClgIGZ1bmN0aW9uIGZyb20gdGhlIGBzZmAgcGFja2FnZSB0byBpbXBvcnQgaXQuIGBzdF9yZWFkYCBpcyBleHRyZW1lbHkgdmVyc2F0aWxlIGFuZCBhYmxlIHRvIGltcG9ydCBtb3N0IHNwYXRpYWwgZGF0YSBmb3JtYXRzIGludG8gUi4gVGhlIG9ubHkgYXJndW1lbnQgdGhhdCBuZWVkcyB0byBiZSBzdXBwbGllZCB0byBgc3RfcmVhZGAgaXMgdGhlIGZ1bGwgcGF0aCB0byB0aGUgVVRMQSBib3VuZGFyaWVzIA0KDQojIyMgRXhlcmNpc2UgLSBsb2FkaW5nIHNoYXBlZmlsZXMNCg0KKiBVc2UgYHN0X3JlYWQoKWAgdG8gbG9hZCB0aGUgVVRMQSBib3VuZGFyaWVzIHlvdSBkb3dubG9hZGVkIGF0IHRoZSBiZWdpbm5pbmcgb2YgdGhlIHR1dG9yaWFsLCBhc2B1dGxhXzIwMTlgLg0KKiBVVExBIHBhdGggLSBgZGF0YS9zaHAvTG9uZG9uXzIwMTkvTG9uZG9uX2FuZF9zdXJyb3VuZGluZ19VVExBcy5zaHBgDQoqIE1ha2UgYSBzdGF0aWMgbWFwIG9mIHRoZSBvYmplY3QgeW91IGhhdmUganVzdCBjcmVhdGVkIHVzaW5nIGBxdG0oKWAgYW5kIHNldHRpbmcgYHRtYXBfbW9kZSgicGxvdCIpYC4NCg0KKipTb2x1dGlvbioqDQpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFfQ0KdXRsYV8yMDE5IDwtIHN0X3JlYWQoImRhdGEvc2hwL0xvbmRvbl8yMDE5L0xvbmRvbl9hbmRfc3Vycm91bmRpbmdfVVRMQXMuc2hwIiwgcXVpZXQgPSBUUlVFKQ0KdG1hcF9tb2RlKCJwbG90IikNCnF0bSh1dGxhXzIwMTkpDQoNCmBgYA0KDQpVVExBIGJvdW5kYXJpZXMgaGF2ZSBsb2FkZWQgY29ycmVjdGx5IGJ1dCB0aGV5IGN1cnJlbnRseSBjb3ZlciBMb25kb24gYXMgd2VsbCBhcyBzb21lIG9mIHRoZSBzdXJyb3VuZGluZyBVVExBcy4gQmVjYXVzZSBzaW1wbGUgZmVhdHVyZSBvYmplY3RzIGFyZSBkYXRhIGZyYW1lcyB3aXRoIGEgZ2VvbWV0cnkgY29sdW1uIGF0dGFjaGVkLCBhbnkgb3BlcmF0aW9ucyB0aGF0IHdlIHdvdWxkIHBlcmZvcm0gb24gYSBub3JtYWwgZGF0YSBmcmFtZSBjYW4gYWxzbyBiZSBwZXJmb3JtZWQgb24gYW4gb2JqZWN0IG9mIGNsYXNzIGBzZmAuIEhlcmUgd2Ugd2lsbCB1c2UgdGhlIGBkcGx5cjo6ZmlsdGVyYCBhbmQgYHN0cmluZ3I6OnN0cl9kZXRlY3QoKWAgZnJvbSB0aGUgdGhlIGB0aWR5dmVyc2VgIHBhY2thZ2UgdG8gb25seSBrZWVwIFVUTEFzIHdob3NlIEdTUyBjb2RlIHN0YXJ0cyB3aXRoICJFMDkiLiAiRTA5IiBkZW5vdGVzIHRoYXQgYW4gZW50aXR5IGlzIGEgTG9uZG9uIEJvcm91Z2guDQoNCg0KIyMjIEV4ZXJjaXNlIC0gZmlsdGVyIHNwYXRpYWwgZGF0YSBieSB2YXJpYWJsZQ0KDQoqIEluc3BlY3QgdXRsYV8yMDE5IHVzaW5nIGBoZWFkKClgIG9yIGBnbGltcHNlKClgLCBhbmQgaWRlbnRpZnkgd2hpY2ggY29sdW1uIGhvbGRzIHRoZSBHU1MgY29kZXMgLSBpdCBzaG91bGQgZW5kIGluICJjZCIuDQoqIENyZWF0ZSBhIG5ldyBvYmplY3QgY2FsbGVkIGBsb25kb25fdXRsYWAuIFVzZSBgZHBseXI6OmZpbHRlcmAgYWxvbmdzaWRlIGBzdHJpbmdyOjpzdHJfZGV0ZWN0KClgIHRvIG9ubHkga2VlcCBvYnNlcnZhdGlvbnMgd2hpY2ggaGF2ZSBhIEdTUyBjb2RlIHN0YXJ0aW5nIHdpdGggIkUwOSIuDQoqIFBsb3QgYGxvbmRvbl91dGxhYCB0byBzZWUgaWYgdGhlIHJlc3VsdHMgbG9vayBjb3JyZWN0Lg0KDQoqKlNvbHV0aW9uKioNCmBgYHtyfQ0KaGVhZCh1dGxhXzIwMTkpDQpgYGANCg0KYGBge3J9DQpsb25kb25fdXRsYSA8LSBmaWx0ZXIodXRsYV8yMDE5LCBzdHJfZGV0ZWN0KGN0eXVhMTljZCwgIkUwOSIpKQ0KcXRtKGxvbmRvbl91dGxhKQ0KYGBgDQoNCkZpbmFsbHksIGZvciB0aGUgbmV4dCBzdGVwLCB3ZSBvbmx5IG5lZWQgdGhlIG91dGVyIGJvdW5kYXJ5IG9mIExvbmRvbiAtIGFsbCB0aGUgaW50ZXJuYWwgVVRMQSBib3VuZGFyaWVzIGhhdmUgdG8gYmUgcmVtb3ZlZCBhbmQgb25seSB0aGUgb3V0ZXIgZWRnZXMga2VwdC4gYHNmYCBoYXMgYSBmdW5jdGlvbiBleGFjdGx5IGZvciB0aGlzIHB1cnBvc2UgY2FsbGVkIGBzdF91bmlvbigpYC4gDQpJdCBvbmx5IHRha2VzIG9uZSBhcmd1bWVudCwgd2hpY2ggaXMgdGhlIGBzZmAgb2JqZWN0IHdlIHdhbnQgdG8gbWVyZ2UuIA0KDQojIyMgRXhlcmNpc2UgLSBkaXNzb2x2ZSBib3VuZGFyaWVzDQoNCiogQ3JlYXRlIGEgbmV3IG9iamVjdCBjYWxsZWQgYGxvbmRvbl9ib3VuZGFyeWAgdXNpbmcgdGhlIGBzdF91bmlvbmAgZnVuY3Rpb24uDQoqIFBsb3QgaXQgdG8gY2hlY2sgdGhlIHJlc3VsdHMuDQoNCioqU29sdXRpb24qKg0KYGBge3J9DQpsb25kb25fYm91bmRhcnkgPC0gc3RfdW5pb24obG9uZG9uX3V0bGEpDQpxdG0obG9uZG9uX2JvdW5kYXJ5KQ0KYGBgDQoNCg0KIyMgU3BhdGlhbCBzdWJzZXR0aW5nIGFuZCBDUlMNCg0KSW4gYWRkaXRpb24gdG8gc3Vic2V0dGluZyBieSB2YWx1ZSwgYXMgd2UgZGlkIHdpdGggdGhlIFVUTEEgYm91bmRhcmllcyBlYXJsaWVyLCB3ZSBjYW4gYWxzbyBzdWJzZXQgb2JzZXJ2YXRpb25zIGJ5IGV2YWx1YXRpbmcgdGhlaXIgc3BhdGlhbCByZWxhdGlvbnNoaXAgd2l0aCBhbm90aGVyIGRhdGEgc2V0LiBXZSBjYW4gZm9yIGV4YW1wbGUgc2VsZWN0IGFsbCBVVExBcyB3aGljaCBhcmUgZnVsbHkgd2l0aGluIFdhbGVzLCBldmVyeSBPdXRwdXQgQXJlYSBpbnRlcnNlY3RlZCBieSBhIHJpdmVyLCBvciBhbGwgaG91c2Vob2xkcyBvdXRzaWRlIG9mIGNpdHkgYm91bmRhcmllcy4gVGhlcmUgYXJlIGEgbnVtYmVyIG9mIGRpZmZlcmVudCBzcGF0aWFsIHJlbGF0aW9uc2hpcHMgd2hpY2ggY2FuIGJlIHRlc3RlZCBhbmQgdXNlZCB0byBzdWJzZXQgb2JzZXJ2YXRpb25zLg0KDQo8Y2VudGVyPg0KIVtdKGRhdGEvaW1nL3NwYXRpYWxfcmVsYXRpb24ucG5nKSANCjwvY2VudGVyPg0KDQoNCmBzZmAgaGFzIGFuIGluYnVpbHQgZnVuY3Rpb24gY2FsbGVkIGBzdF9maWx0ZXIoKWAgd2hpY2ggd2UgY2FuIHVzZSB0byBzcGF0aWFsbHkgc3Vic2V0IG9ic2VydmF0aW9ucy4NClRoZSBmdW5jdGlvbiB0YWtlcyBzZXZlcmFsIGFyZ3VtZW50czoNCg0KKiB4IC0gYHNmYCBkYXRhIGZyYW1lIHdlIHdhbnQgdG8gc3Vic2V0IC0gYGxmYl9zZmANCiogeSAtIGBzZmAgb2JqZWN0IHVzZWQgdG8gZXZhbHVhdGUgdGhlIHNwYXRpYWwgcmVsYXRpb25zaGlwIC0gYGxvbmRvbl9ib3VuZGFyeWANCg0KQmVmb3JlIHJ1bm5pbmcgYW55IHNwYXRpYWwgb3BlcmF0aW9ucyBvbiB0d28gc3BhdGlhbCBvYmplY3RzIGl0IGlzIGFsd2F5cyB3b3J0aCBjaGVja2luZyBpZiB0aGVpciBjb29yZGluYXRlIHJlZmVyZW5jZSBzeXN0ZW1zIChDUlMpIG1hdGNoLiBgc2ZgIHdpbGwgdGhyb3cgYW4gZXJyb3IgaWYgdGhhdCdzIG5vdCB0aGUgY2FzZS4gVHJ5IGl0IGZvciB5b3Vyc2VsZiBiZWxvdy4NCg0KIyMjIEV4ZXJjaXNlIC0gc3BhdGlhbCBzdWJzZXQgcGFydCAxIA0KDQoqIFVzZSBgc3RfZmlsdGVyKClgIHRvIHNwYXRpYWxseSBzdWJzZXQgYGxmYl9zZmAgYnkgdGVzdGluZyBpdHMgcmVsYXRpb25zaGlwIHdpdGggYGxvbmRvbl9ib3VuZGFyeWAuDQoNCioqU29sdXRpb246KioNCmBgYHtyLCBldmFsPUZBTFNFfQ0KbGZiX3NmIDwtIHN0X2ZpbHRlcih4ID0gbGZiX3NmLCB5ID0gbG9uZG9uX2JvdW5kYXJ5KQ0KDQpgYGANCg0KWW91IHNob3VsZCBoYXZlIGdvdCBhbiBlcnJvciBoZXJlIHNheWluZyBgeCBzdF9jcnMoeCkgPT0gc3RfY3JzKHkpIGlzIG5vdCBUUlVFYC4gSXQgbWVhbnMgdGhhdCBvYmplY3RzIHggYW5kIHkgaGF2ZSBkaWZmZXJlbnQgY29vcmRpbmF0ZSByZWZlcmVuY2Ugc3lzdGVtcy4gU3BhdGlhbCBvcGVyYXRpb25zIHJlcXVpcmUgYWxsIG9iamVjdHMgdG8gaGF2ZSB0aGUgc2FtZSBDUlMuIFdlIGNhbiBzZWUgdGhpcyBmb3Igb3Vyc2VsdmVzIGJ5IHJ1bm5pbmcgdGhlIGBzdF9jcnMoKWAgZnVuY3Rpb24sIHdoaWNoIHJldHVybnMgdGhlIGNvb3JkaW5hdGUgcmVmZXJlbmNlIHN5c3RlbSBvZiBhbiBvYmplY3QuDQoNCiMjIyBFeGVyY2lzZSAgLSBjaGVjayBDUlMNCg0KKiBSdW4gYHN0X2NycygpYCBvbiBib3RoIGFuZCBgbGZiX3NmYCBhbmQgYGxvbmRvbl9ib3VuZGFyeWAgYW5kIGNvbXBhcmUgdGhlIHJlc3VsdHMuDQoNCioqU29sdXRpb246KioNCmBgYHtyfQ0Kc3RfY3JzKGxmYl9zZilbWzFdXQ0KDQpzdF9jcnMobG9uZG9uX2JvdW5kYXJ5KVtbMV1dDQoNCmBgYA0KDQpgc3RfY3JzKClgIHByb3ZpZGVzIGRldGFpbGVkIGluZm9ybWF0aW9uIGFib3V0IHRoZSBDUlMgYW5kIHByb2plY3Rpb24gb2YgZGF0YSwgYnV0IGFsbCB3ZSBuZWVkIHRvIGNoZWNrIGlzIGl0cyBmaXJzdCBlbGVtZW50IGRlbm90ZWQgYnkgYFtbMV1dYC4gV2UgY2FuIHNlZSB0aGF0IGBsZmJfc2ZgIHVzZXMgYEVQU0c6Mjc3MDBgLCB3aGlsZSBgbG9uZG9uX2JvdW5kYXJ5YCBpcyBzZXQgdG8gYFdHUyA4NGAuIFRoaXMgcHJvYmxlbSBjYW4gYmUgc29sdmVkIGJ5IHRyYW5zZm9ybWluZyBgbG9uZG9uX2JvdW5kYXJ5YCdzIENSUyB0byBtYXRjaCB0aGF0IG9mIGBsZmJfc2ZgLCBzaW1wbHkgYnkgdXNpbmcgdGhlIGNvcnJlY3QgRVBTRyBjb2RlLiBUbyBkbyBzbyB3ZSB3aWxsIHVzZSB0aGUgYHN0X3RyYW5zZm9ybSgpYCBmdW5jdGlvbiB3aGljaCB0YWtlcyB0d28gYXJndW1lbnRzOiANCg0KKiB4IC0gYHNmYCBvYmplY3QgdG8gYmUgdHJhbnNmb3JtZWQNCiogY3JzIC0gRVBTRyBjb2RlIHRoYXQgd2Ugd2FudCB0byB0cmFuc2Zvcm0gb3VyIGRhdGEgdG8gLSBCTkcgaXMgMjc3MDAuDQoNCiMjIyBFeGVyY2lzZSAtIHRyYW5zZm9ybSBDUlMNCg0KKiBSdW4gYHN0X3RyYW5zZm9ybSgpYCB0byB0cmFuc2Zvcm0gYW5kIG92ZXJ3cml0ZSBgbG9uZG9uX2JvdW5kYXJ5YC4gUmVtZW1iZXIgdG8gc2V0IHRoZSBjb3JyZWN0IENSUy4gDQoqIFJ1biBgc3RfY3JzKClgIG9uIGBsZmJfc2ZgIGFuZCBuZXdseSB0cmFuc2Zvcm1lZCBgbG9uZG9uX2JvdW5kYXJ5YCBhbmQgY29tcGFyZSB0aGUgcmVzdWx0cy4NCg0KKipTb2x1dGlvbjoqKg0KDQpgYGB7cn0NCmxvbmRvbl9ib3VuZGFyeSA8LSBzdF90cmFuc2Zvcm0obG9uZG9uX2JvdW5kYXJ5LCBjcnMgPSAyNzcwMCkNCg0Kc3RfY3JzKGxmYl9zZilbWzFdXQ0Kc3RfY3JzKGxvbmRvbl9ib3VuZGFyeSlbWzFdXQ0KYGBgDQoNCg0KTm93IHRoYXQgdGhlIENSUyBhcmUgbWF0Y2hpbmcgd2Ugc2hvdWxkIGJlIGFibGUgdG8gc3BhdGlhbGx5IHN1YnNldCBgbGZiX3NmYC4NCg0KIyMjIEV4ZXJjaXNlIC0gc3BhdGlhbCBzdWJzZXQgcGFydCAyIA0KDQoqIFVzZSBgc3RfZmlsdGVyYCB0byBzcGF0aWFsbHkgc3Vic2V0IGBsZmJfc2ZgIGJ5IHRlc3RpbmcgaXRzIHJlbGF0aW9uc2hpcCB3aXRoIGBsb25kb25fYm91bmRhcnlgLiBPdmVyd3JpdGUgYGxmYl9zZmAgd2l0aCB0aGUgc3Vic2V0IGRhdGEuDQoqIFBsb3QgaXQgdG8gY2hlY2sgaWYgdGhlIHJlc3VsdHMgYXJlIGNvcnJlY3QgLSBhbGwgcG9pbnRzIHNob3VsZCBiZSB3aXRoaW4gTG9uZG9uLg0KDQoqKlNvbHV0aW9uOioqDQpgYGB7cn0NCmxmYl9zZiA8LSBzdF9maWx0ZXIoeCA9IGxmYl9zZiwgeSA9IGxvbmRvbl9ib3VuZGFyeSkNCiBxdG0obG9uZG9uX2JvdW5kYXJ5KSArIHF0bShsZmJfc2YpDQpgYGANCg0KDQojIyBTcGF0aWFsIGFuZCBub24tc3BhdGlhbCBqb2lucw0KDQpTaW1wbGUgZmVhdHVyZXMgZGF0YSBjYW4gYmUgam9pbmVkIHRvIG90aGVyIGRhdGEgc2V0cyBpbiB0d28gd2F5cy4gV2UgY2FuIGVpdGhlciB1c2UgYSB0cmFkaXRpb25hbCwgU1FMIGxpa2Ugam9pbiwgYmFzZWQgb24gYSB2YWx1ZSBzaGFyZWQgYWNyb3NzIHRoZSBkYXRhIHNldHMgb3IsIHNpbmNlIHdlIGhhdmUgYSBnZW9tZXRyeSBjb2x1bW4sIG9uIHRoZSBzcGF0aWFsIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZSBkYXRhIHNldHMuIFRoaXMgaXMga25vd24gYXMgYSBzcGF0aWFsIGpvaW4sIHdoZXJlIHZhcmlhYmxlcyBmcm9tIG9uZSBkYXRhIHNldCBhcmUgam9pbmVkIHRvIGFub3RoZXIgb25seSBvbiB0aGUgYmFzaXMgb2YgdGhlaXIgc3BhdGlhbCByZWxhdGlvbnNoaXAuIFRoZSBtb3N0IGNvbW1vbmx5IHVzZWQgb3BlcmF0aW9uIGlzIGtub3duIGFzIGEgUG9pbnQtaW4tUG9seWdvbiBqb2luIHdoZXJlIGRhdGEgZnJvbSBhIHBvbHlnb24gaXMgam9pbmVkIHRvIHRoZSBwb2ludHMgd2l0aGluIHRoZW0uDQoNCjxjZW50ZXI+DQohW10oZGF0YS9pbWcvc3BhdGlhbF9qb2luLnBuZykNCjwvY2VudGVyPiAgDQogIA0KICANCg0KSW4gYHNmYCBzcGF0aWFsIGpvaW5zIGFyZSBoYW5kbGVkIHVzaW5nIHRoZSBgc3Rfam9pbih4LCB5KWAgZnVuY3Rpb24gd2l0aCBhcmd1bWVudHM6DQoNCiogeCAtIGBzZmAgb2JqZWN0IHRvIHdoaWNoIHdlIGFyZSBqb2luaW5nIGRhdGEgKExIUyBpbiBTUUwpDQoqIHkgLSBgc2ZgIG9iamVjdCB3aG9zZSB2YXJpYWJsZXMgYXJlIGJlaW5nIGpvaW5lZCAoUkhTIGluIFNRTCkNCg0KV2Ugd2lsbCBiZSBqb2luaW5nIHRoZSBNaWRkbGUgU3VwZXIgT3V0cHV0IEFyZWFzIHRvIExGQiBsb2NhdGlvbnMsIHdoaWNoIHdpbGwgdGhlbiBhbGxvdyB1cyB0byBncm91cCBhbmQgcGxvdCBkYXRhIGF0IE1TT0EgbGV2ZWwuIEl0J3MgaW1wb3J0YW50IHRvIG5vdGUgdGhhdCB3ZSBhcmUgdXNpbmcgdHdvIGRpZmZlcmVudCBzZXRzIG9mIE1TT0EgYm91bmRhcmllcyAtIG9uZSB0byBwZXJmb3JtIGEgc3BhdGlhbCBqb2luLCBhbmQgYW5vdGhlciB0byB2aXN1YWxpc2UgdGhlIGRhdGEuIEZ1bGwgRXh0ZW50IChCRkUpIGJvdW5kYXJpZXMgYXJlIHVzZWQgZm9yIHRoZSBmb3JtZXIuIFRoaXMgZW5zdXJlcyB0aGF0IGFsbCBwb2ludHMgYXJlIGNvcnJlY3RseSBqb2luZWQgdG8gTVNPQSBib3VuZGFyaWVzLCBhbmQgdGhhdCB3ZSBkb24ndCBsb3NlIGFueSBkYXRhIGFyb3VuZCBib2RpZXMgb2Ygd2F0ZXIuIFRvIHBsb3QgdGhlIGRhdGEgd2UgdXNlIFN1cGVyIEdlbmVyYWxpc2VkIENsaXBwZWQgKEJTQykgYm91bmRhcmllcy4gVGhleSBhcmUgc21hbGxlciBpbiBzaXplLCBsb2FkIGZhc3RlciwgYW5kLCBiZWNhdXNlIHRoZXkncmUgY2xpcHBlZCB0byB0aGUgY29hc3RsaW5lLCBhcHBlYXIgaW4gYSB3YXkgdGhhdCdzIGZhbWlsaWFyIHRvIG1vc3QgdXNlcnMuDQoNCiMjIyBFeGVyY2lzZSAtIHNwYXRpYWwgam9pbnMNCg0KKiBSZWFkIGluIEJGRSBib3VuZGFyaWVzYGRhdGEvc2hwL01TT0FfMjAxMV9sb25kb24vTVNPQV8yMDExX0JGRV9Mb25kb24uZ3BrZ2AgYXMgYG1zb2FfbG9uZG9uX0JGRWAgLSB1c2UgYHN0X3JlYWQoKWANCiogT3ZlcndyaXRlIGBsZmJfc2ZgIGJ5IHJ1bm5pbmcgYHN0X2pvaW4oKWAgYmV0d2VlbiBgbGZiX3NmYCBhbmQgYG1zb2FfbG9uZG9uX0JGRWANCiogSW5zcGVjdCBpdCB1c2luZyBgaGVhZCgpYCBvciBgZ2xpbXBzZSgpYCB0byBzZWUgd2hhdCBjb2x1bW5zIGhhdmUgYmVlbiBhZGRlZC4NCg0KYGBge3J9DQptc29hX2xvbmRvbl9CRkUgPC0gc3RfcmVhZCgiZGF0YS9zaHAvTVNPQV8yMDExX2xvbmRvbi9NU09BXzIwMTFfQkZFX0xvbmRvbi5ncGtnIiwgcXVpZXQgPSBUUlVFKQ0KDQpsZmJfc2YgPC0gc3Rfam9pbihsZmJfc2YsIG1zb2FfbG9uZG9uX0JGRSkgDQoNCmhlYWQobGZiX3NmKQ0KYGBgDQoNCg0KTm93IHRoYXQgYE1TT0ExMUNEYCBpcyBhdHRhY2hlZCB0byBvdXIgb2JzZXJ2YXRpb25zIHdlIGNhbiBjcmVhdGUgc3VtbWFyeSBzdGF0aXN0aWNzIGZvciBlYWNoIE1TT0EuIEFzIG1lbnRpb25lZCBiZWZvcmUsIHdlIGNhbiB1c2Ugc3RhbmRhcmQgYHRpZHl2ZXJzZWAgZnVuY3Rpb25zIG9uIGBzZmAgb2JqZWN0cy4gSGVyZSwgd2Ugd2lsbCB1c2UgYGRwbHlyYCB0byBjYWxjdWxhdGUgdGhlIHRvdGFsIG51bWJlciBvZiBpbmNpZGVudHMgYW5kIHRoZWlyIGNvc3QsIGFuZCB0aGVuIHVzZSBhIG5vbiBzcGF0aWFsIGpvaW4gdG8gYXR0YWNoIHRob3NlIHJlc3VsdHMgdG8gTVNPQSBib3VuZGFyaWVzLiBBdCB0aGlzIHN0YWdlIHdlIG5vIGxvbmdlciBuZWVkIHRoZSBnZW9tZXRyeSBjb2x1bW4gZm9yIGVhY2ggTEZCIGluY2lkZW50IGFzIGEpIHdlJ3JlIG5vdCBwZXJmb3JtaW5nIGFueSBzcGF0aWFsIG9wZXJhdGlvbnMgb24gb3VyIHBvaW50cywgYW5kIGIpIHRoZSBnZW9tZXRyeSBjb2x1bW4gY2FuIHNsb3cgZG93bi9pbnRlcnJ1cHQgdGhlIGBkcGx5cjo6Z3JvdXBfYnlgIGZ1bmN0aW9uIHdoaWNoIHdlIHdpbGwgYmUgdXNpbmcuIFRvIHJlbW92ZSB0aGUgZ2VvbWV0cnkgY29sdW1uIHdlIHVzZSB0aGUgYHN0X2Ryb3BfZ2VvbWV0cnkoKWAgZnVuY3Rpb24gZGlyZWN0bHkgaW4gdGhlIGBkcGx5cmAgcGlwZS4gDQoNCiMjIyBFeGVyY2lzZSAtIE1TT0Egc3VtbWFyeSBzdGF0aXN0aWNzDQoNCiogUmVhZCBpbiBCU0MgYm91bmRhcmllc2BkYXRhL3NocC9NU09BXzIwMTFfbG9uZG9uL01TT0FfMjAxMV9CU0NfTG9uZG9uLnNocGAgYXMgYG1zb2FfbG9uZG9uX0JTQ2AgLSB1c2UgYHN0X3JlYWQoKWANCiogVXNlIGBzdF9kcm9wX2dlb21ldHJ5KClgIG9uIGBsZmJfc2ZgIHRvIHJlbW92ZSBnZW9tZXRyeSBkYXRhLiANCiogQ3JlYXRlIHN1bW1hcnkgc3RhdGlzdGljcyBwZXIgTVNPQSAtIHN1bSBvZiBgY29zdF9nYnBgIGFzIGB0b3RhbF9jb3N0YCAodXNlIGBuYS5ybSA9IFRSVUVgKSwgYW5kIHRoZSB0b3RhbCBudW1iZXIgb2YgaW5jaWRlbnRzIGFzIGBuX2Nhc2VzYC4gWW91IHdpbGwgbmVlZCB0byB1c2UgYGdyb3VwX2J5KClgIGFuZCBgc3VtbWFyaXNlKClgDQoqIFVzZSBgbXV0YXRlYCB0byBjcmVhdGUgYSBuZXcgY29sdW1uIGNhbGxlZCBgY29zdF9wZXJfaW5jaWRlbnRgIHdoaWNoIGlzIGVxdWFsIHRvIGB0b3RhbF9jb3N0YCBkaXZpZGVkIGJ5IGBuX2Nhc2VzYC4NCiogSm9pbiBgbGZiX21zb2Ffc3RhdHNgIHRvIGBtc29hX2xvbmRvbl9CU0NgLCB1c2luZyBgbGVmdF9qb2luKClgIGFuZCBjcmVhdGUgYSBuZXcgb2JqZWN0IGBtc29hX2xmYmANCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQptc29hX2xvbmRvbl9CU0MgPC0gc3RfcmVhZCgiZGF0YS9zaHAvTVNPQV8yMDExX2xvbmRvbi9NU09BXzIwMTFfQlNDX0xvbmRvbi5zaHAiLCBxdWlldCA9IFRSVUUpIA0KDQpsZmJfbXNvYV9zdGF0cyA8LSBsZmJfc2YgJT4lIA0KICAgICAgICAgICAgICAgICAgc3RfZHJvcF9nZW9tZXRyeSgpICU+JSANCiAgICAgICAgICAgICAgICAgIGdyb3VwX2J5KE1TT0ExMUNEKSAlPiUgDQogICAgICAgICAgICAgICAgICBzdW1tYXJpc2UodG90YWxfY29zdCA9IHN1bShjb3N0X2dicCwgbmEucm09VFJVRSksIG5fY2FzZXMgPSBuKCkpICU+JSANCiAgICAgICAgICAgICAgICAgIG11dGF0ZShjb3N0X3Blcl9pbmNpZGVudCA9IHRvdGFsX2Nvc3Qvbl9jYXNlcykNCiAgICAgICAgICAgICAgICAgICAgICAgICANCm1zb2FfbGZiIDwtIGxlZnRfam9pbihtc29hX2xvbmRvbl9CU0MsIGxmYl9tc29hX3N0YXRzKQ0KaGVhZChtc29hX2xmYikNCmBgYA0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCg0Kcm0obXNvYV9sb25kb25fQkZFKQ0Kcm0obXNvYV9sb25kb25fQlNDKQ0KDQpybShsZmJfbXNvYV9zdGF0cykNCnJtKGxmYl9zZikNCmBgYA0KDQpBdCB0aGlzIHN0YWdlIGl0IGlzIGEgZ29vZCBpZGVhIHRvIHNhdmUgb3VyIGRhdGEuIFdlIGNhbiBkbyB0aGlzIHVzaW5nIHRoZSBgc3Rfd3JpdGUoKWAgZnVuY3Rpb24uIEl0IG5lZWRzIGFuIGBzZmAgb2JqZWN0IGFuZCB0aGUgcGF0aCBhbmQgbmFtZSBvZiB0aGUgb3V0cHV0Lg0KDQojIyMgRXhlcmNpc2UgLSBzYXZlIGRhdGEgdG8gZ3BrZw0KDQoqIENvcHkgYW5kIGV4ZWN1dGUgdGhlIGZvbGxvd2luZyBjb2RlIHRvIHNhdmUgeW91ciBkYXRhOiBgc3Rfd3JpdGUobXNvYV9sZmIsIm91dHB1dC9tc29hX2xmYi5ncGtnKWANCg0KDQojIE1ha2luZyBiZXR0ZXIgbWFwcw0KIyMgSW50cm9kdWN0aW9uIHRvIHRtYXANCg0KTm93IHRoYXQgd2UgaGF2ZSBwcm9jZXNzZWQgb3VyIGRhdGEgd2UgY2FuIHN0YXJ0IG1hcHBpbmcgaXQuIFNvIGZhciB3ZSBoYXZlIG9ubHkgdXNlZCB0aGUgYHF0bSgpYCBmdW5jdGlvbiBmcm9tIHRoZSBgdG1hcGAgcGFja2FnZS4gVGhpcyBjcmVhdGVzIGEgZGVmYXVsdCBtYXAgYW5kIGlzIGdyZWF0IHdoZW4gYWxsIHdlIHdhbnQgdG8gZG8gaXMgcXVpY2tseSB2aXN1YWxpc2Ugb3VyIGRhdGEuIFRoZSBmdWxsIHJhbmdlIG9mIGB0bWFwYCBmdW5jdGlvbnMgZ2l2ZXMgdXMgY29udHJvbCBvdmVyIGFsbCBlbGVtZW50cyBvZiB0aGUgZmluYWwgcGxvdCBhbmQgYWxsb3dzIHVzIHRvIGNyZWF0ZSBoaWdoIHF1YWxpdHkgbWFwcy4NCg0KYHRtYXBgIGZvbGxvd3Mgc2ltaWxhciBwcmluY2lwbGVzIHRvIGBnZ3Bsb3QyYCwgd2hlcmUgd2UgZmlyc3Qgc3BlY2lmeSB0aGUgZGF0YSBzb3VyY2UgLSBgdG1fc2hhcGVgLCB0aGVuIHRoZSBhZXN0aGV0aWNzIG9mIHRoZSBwbG90IC0gYHRtX3BvbHlnb25zYCwgYHRtX2RvdHNgLCBldGMuLCBhbmQgdGhlbiB3ZSBtYWtlIGFueSBmaW5hbCBhZGp1c3RtZW50cyB0byB0aGUgb3ZlcmFsbCBsb29rIC0gYHRtX2xheW91dGAuIEFsbCBmdW5jdGlvbnMgbmVlZCB0byBiZSBjb25uZWN0ZWQgdXNpbmcgdGhlIGArYCBzeW1ib2wuDQoNCiogYHRtX3NoYXBlKClgIC0gYHNmYCBvYmplY3Qgd2hpY2ggeW91IHdhbnQgdG8gcGxvdA0KKiBgdG1fZmlsbCgpYCwgYHRtX2JvcmRlcnMoKWAsIGB0bV9wb2x5Z29ucygpYCwgYHRtX2RvdHMoKWAgLSB0eXBlcyBvZiBvdXRwdXQNCiogYHRtX2xheW91dCgpYCAtIGNvbnRyb2xzIGxheW91dCBvZiB0aGUgbWFwLCB0aXRsZXMsIGxhYmVscywgZXRjLg0KDQpgdG1hcGAgc3ludGF4OiBgdG1fc2hhcGUoc2Zfb2JqZWN0KSArIHRtX2JvcmRlcnMoY29sID0gZWl0aGVyICJjb2xvdXIiIG9yIG5hbWUgb2YgY29sdW1uIHdoaWNoIHdlIHdhbnQgdG8gcGxvdCkgKyB0bV9sYXlvdXQobWFpbi50aXRsZSA9ICJ0aXRsZSBvZiB5b3VyIG1hcCIpYA0KDQojIyBHdWlkZWQgZXhlcmNpc2UgLSBtYXBwaW5nDQoNClN0YXJ0IGJ5IHNwZWNpZnlpbmcgd2hpY2ggYHNmYCBvYmplY3QgaXMgYmVpbmcgbWFwcGVkIGluIGB0bV9zaGFwZSgpYCBhbmQgd2hhdCBjb2x1bW4gaG9sZHMgdGhlIHZhbHVlcyB0byBiZSB2aXN1YWxpc2VkLiBXZSB3aWxsIGFsc28gY2hhbmdlIHRoZSBsZWdlbmQncyB0aXRsZS4NCmBgYHtyfQ0KdG1fc2hhcGUobXNvYV9sZmIpICsgDQogIHRtX3BvbHlnb25zKGNvbCA9ICJjb3N0X3Blcl9pbmNpZGVudCIsIHRpdGxlID0gIkNvc3QgcGVyIEluY2lkZW50ICjCoykiKQ0KYGBgDQoNCk5vdyBsZXQncyBhZGQgYGxvbmRvbl9ib3VuZGFyeWAgdG8gaGF2ZSBhIHRoaWNrZXIgbGluZSBhcm91bmQgTG9uZG9uLg0KYGBge3J9DQp0bV9zaGFwZShtc29hX2xmYikgKyANCiAgdG1fcG9seWdvbnMoY29sID0gImNvc3RfcGVyX2luY2lkZW50IiwgdGl0bGUgPSAiQ29zdCBwZXIgSW5jaWRlbnQgKMKjKSIpICsgDQogIHRtX3NoYXBlKGxvbmRvbl9ib3VuZGFyeSkgKyB0bV9ib3JkZXJzKGNvbCA9ICJibGFjayIsIGx3ZCA9IDIpDQpgYGANCg0KTmV4dCB3ZSB3aWxsIGFkZCBhIHNjYWxlIGJhciBhbmQgcG9zaXRpb24gaXQgaW4gdGhlIGJvdHRvbSBsZWZ0IGNvcm5lci4NCmBgYHtyfQ0KdG1fc2hhcGUobXNvYV9sZmIpICsgDQogIHRtX3BvbHlnb25zKGNvbCA9ICJjb3N0X3Blcl9pbmNpZGVudCIsIHRpdGxlID0gIkNvc3QgcGVyIEluY2lkZW50ICjCoykiKSArIA0KICB0bV9zaGFwZShsb25kb25fYm91bmRhcnkpICsgdG1fYm9yZGVycyhjb2wgPSAiYmxhY2siLCBsd2QgPSAyKSArDQogIHRtX3NjYWxlX2Jhcihwb3NpdGlvbiA9IGMoMCwwKSkNCmBgYA0KDQpXZSBjYW4gbm93IHJlbW92ZSB0aGUgYmxhY2sgZnJhbWUgZnJvbSB0aGUgbWFwIGFuZCBhZGQgYSB0aXRsZSB0byBvdXIgbWFwLg0KYGBge3J9DQp0bV9zaGFwZShtc29hX2xmYikgKyANCiAgdG1fcG9seWdvbnMoY29sID0gImNvc3RfcGVyX2luY2lkZW50IiwgdGl0bGUgPSAiQ29zdCBwZXIgSW5jaWRlbnQgKMKjKSIpICsgDQogIHRtX3NoYXBlKGxvbmRvbl9ib3VuZGFyeSkgKyB0bV9ib3JkZXJzKGNvbCA9ICJibGFjayIsIGx3ZCA9IDIpICsNCiAgdG1fc2NhbGVfYmFyKHBvc2l0aW9uID0gYygwLDApKSArDQogICB0bV9sYXlvdXQodGl0bGUgPSAiQXZlcmFnZSBjb3N0IG9mIGFuaW1hbCByZWxhdGVkIGluY2lkZW50cyBiZXR3ZWVuIDIwMDkgYW5kIDIwMjAiLCAgDQogICAgICAgICAgICBmcmFtZSA9IEZBTFNFKQ0KYGBgDQoNCkFsbCBvZiB0aGUgbWFwIGVsZW1lbnRzIGFyZSBub3cgdmlzaWJsZSBidXQgdGhleSdyZSBub3QgaW4gdGhlIHJpZ2h0IHBsYWNlLiBXZSBjYW4gc29sdmUgdGhpcyBieSBpbmNyZWFzaW5nIHRoZSBtYXJnaW5zIGFyb3VuZCBvdXIgbWFwLiBUaGlzIHdpbGwgYWxsb3cgdGhlIHRpdGxlIGFuZCB0aGUgbGVnZW5kIHRvIG1vdmUgb3V0d2FyZHMuDQoNCmBgYHtyfQ0KdG1fc2hhcGUobXNvYV9sZmIpICsgDQogIHRtX3BvbHlnb25zKGNvbCA9ICJjb3N0X3Blcl9pbmNpZGVudCIsIHRpdGxlID0gIkNvc3QgcGVyIEluY2lkZW50ICjCoykiKSArIA0KICB0bV9zaGFwZShsb25kb25fYm91bmRhcnkpICsgdG1fYm9yZGVycyhjb2wgPSAiYmxhY2siLCBsd2QgPSAyKSArDQogIHRtX3NjYWxlX2Jhcihwb3NpdGlvbiA9IGMoMCwwKSkgKw0KICAgdG1fbGF5b3V0KHRpdGxlID0gIkF2ZXJhZ2UgY29zdCBvZiBhbmltYWwgcmVsYXRlZCBpbmNpZGVudHMgYmV0d2VlbiAyMDA5IGFuZCAyMDIwIiwgIA0KICAgICAgICAgICAgZnJhbWUgPSBGQUxTRSwgaW5uZXIubWFyZ2lucyA9IGMoMC4xLDAuMSwwLjEsMC4xNSkpDQoNCmBgYA0KDQoNCmB0bWFwYCBicmVha3MgdXAgbnVtZXJpY2FsIGRhdGEgaW50byBldmVubHkgc2l6ZWQgY2F0ZWdvcmllcyBieSBkZWZhdWx0LCBidXQgeW91IGNhbiBwcm92aWRlIGl0IHdpdGggY3VzdG9tIGJyZWFrcyBhcyB3ZWxsLiANCmBgYHtyfQ0KdG1fc2hhcGUobXNvYV9sZmIpICsgDQogIHRtX3BvbHlnb25zKGNvbCA9ICJjb3N0X3Blcl9pbmNpZGVudCIsIGJyZWFrcyA9IHNlcSgwLDEyMDAsMzAwKSwgdGl0bGUgPSAiQ29zdCBwZXIgSW5jaWRlbnQgKMKjKSIpICsgDQogIHRtX3NoYXBlKGxvbmRvbl9ib3VuZGFyeSkgKyB0bV9ib3JkZXJzKGNvbCA9ICJibGFjayIsIGx3ZCA9IDIpICsNCiAgdG1fc2NhbGVfYmFyKHBvc2l0aW9uID0gYygwLDApKSArDQogICB0bV9sYXlvdXQodGl0bGUgPSAiQXZlcmFnZSBjb3N0IG9mIGFuaW1hbCByZWxhdGVkIGluY2lkZW50cyBiZXR3ZWVuIDIwMDkgYW5kIDIwMjAiLCAgDQogICAgICAgICAgICBmcmFtZSA9IEZBTFNFLCBpbm5lci5tYXJnaW5zID0gYygwLjEsMC4xLDAuMSwwLjE1KSkNCmBgYA0KDQpZb3UgY2FuIGFsc28gdXNlIG90aGVyIG1ldGhvZHMgb2YgYXV0b21hdGljIGRhdGEgY2F0ZWdvcmlzYXRpb24uIGBKZW5rc2AgaXMgYSBwb3B1bGFyIG1ldGhvZCBmb3IgY2x1c3RlcmluZyBkYXRhIGludG8gY2xhc3Nlcy4gWW91IGNhbiBzZXQgdGhlIG51bWJlciBvZiBkZXNpcmVkIGNsYXNzZXMgdXNpbmcgdGhlIGBuYCBhcmd1bWVudCwgYW5kIHRoZSBleGFjdCBtZXRob2Qgd2l0aCB0aGUgYHN0eWxlYCBhcmd1bWVudC4NCmBgYHtyfQ0KdG1fc2hhcGUobXNvYV9sZmIpICsgDQogIHRtX3BvbHlnb25zKGNvbCA9ICJjb3N0X3Blcl9pbmNpZGVudCIsIG4gPSA0LCBzdHlsZSA9ImplbmtzIiwgdGl0bGUgPSAiQ29zdCBwZXIgSW5jaWRlbnQgKMKjKSIpICsgDQogIHRtX3NoYXBlKGxvbmRvbl9ib3VuZGFyeSkgKyB0bV9ib3JkZXJzKGNvbCA9ICJibGFjayIsIGx3ZCA9IDIpICsNCiAgdG1fc2NhbGVfYmFyKHBvc2l0aW9uID0gYygwLDApKSArDQogICB0bV9sYXlvdXQodGl0bGUgPSAiQXZlcmFnZSBjb3N0IG9mIGFuaW1hbCByZWxhdGVkIGluY2lkZW50cyBiZXR3ZWVuIDIwMDkgYW5kIDIwMjAiLCAgDQogICAgICAgICAgICBmcmFtZSA9IEZBTFNFLCBpbm5lci5tYXJnaW5zID0gYygwLjEsMC4xLDAuMSwwLjE1KSkNCmBgYA0KDQoNCldlIHdpbGwgdXNlIHRoZSBtYW51YWxseSBzZXQgYnJlYWtzIHRvIGVuc3VyZSBjb25zaXN0ZW50IHJlc3VsdHMuDQpJdCdzIGFsc28gaW1wb3J0YW50IHRvIGNoYW5nZSB0aGUgbGVnZW5kIGxhYmVscyB0byBlbnN1cmUgdGhlcmUgYXJlIG5vIG92ZXJsYXBwaW5nIHZhbHVlcy4NCmBgYHtyfQ0KdG1fc2hhcGUobXNvYV9sZmIpICsgDQogIHRtX3BvbHlnb25zKGNvbCA9ICJjb3N0X3Blcl9pbmNpZGVudCIsICBicmVha3MgPSBzZXEoMCwxMjAwLDMwMCksIHRpdGxlID0gIkNvc3QgcGVyIEluY2lkZW50ICjCoykiLA0KICAgICAgICAgICAgICBsYWJlbHMgPSBjKCIwIC0gMzAwIiwgIj4zMDAgLSA2MDAiLCAiPjYwMCAtIDkwMCIsICI+OTAwIC0gMTIwMCIpKSArIA0KICB0bV9zaGFwZShsb25kb25fYm91bmRhcnkpICsgdG1fYm9yZGVycyhjb2wgPSAiYmxhY2siLCBsd2QgPSAyKSArDQogIHRtX3NjYWxlX2Jhcihwb3NpdGlvbiA9IGMoMCwwKSkgKw0KICAgdG1fbGF5b3V0KHRpdGxlID0gIkF2ZXJhZ2UgY29zdCBvZiBhbmltYWwgcmVsYXRlZCBpbmNpZGVudHMgYmV0d2VlbiAyMDA5IGFuZCAyMDIwIiwgIA0KICAgICAgICAgICAgZnJhbWUgPSBGQUxTRSwgaW5uZXIubWFyZ2lucyA9IGMoMC4xLDAuMSwwLjEsMC4xNSkpDQpgYGANCg0KDQpGaW5hbGx5IGxldCdzIGNoYW5nZSB0aGUgY29sb3VyIG9mIG91ciBtYXAgYW5kIGluY3JlYXNlIHRoZSBjb250cmFzdC4gQ2hvb3NlIGEgY29sb3VyIGZyb20gW1IgQ29sb3Vyc10oaHR0cHM6Ly93d3cuci1ncmFwaC1nYWxsZXJ5LmNvbS8zOC1yY29sb3JicmV3ZXJzLXBhbGV0dGVzX2ZpbGVzL2ZpZ3VyZS1odG1sL3RoZWNvZGUtMS5wbmcpLg0KDQpgYGB7cn0NCnRtX3NoYXBlKG1zb2FfbGZiKSArIA0KICB0bV9wb2x5Z29ucyhjb2wgPSAiY29zdF9wZXJfaW5jaWRlbnQiLCAgYnJlYWtzID0gc2VxKDAsMTIwMCwzMDApLCB0aXRsZSA9ICJDb3N0IHBlciBJbmNpZGVudCAowqMpIiwNCiAgICAgICAgICAgICAgbGFiZWxzID0gYygiICAwIC0gMzAwIiwgIj4zMDAgLSA2MDAiLCAiPjYwMCAtIDkwMCIsICI+OTAwIC0gMTIwMCIpLCBwYWxldHRlID0gIkJsdWVzIiwgY29udHJhc3QgPSAxKSArIA0KICB0bV9zaGFwZShsb25kb25fYm91bmRhcnkpICsgdG1fYm9yZGVycyhjb2wgPSAiYmxhY2siLCBsd2QgPSAyKSArDQogIHRtX3NjYWxlX2Jhcihwb3NpdGlvbiA9IGMoMCwwKSkgKw0KICAgdG1fbGF5b3V0KHRpdGxlID0gIkF2ZXJhZ2UgY29zdCBvZiBhbmltYWwgcmVsYXRlZCBpbmNpZGVudHMgYmV0d2VlbiAyMDA5IGFuZCAyMDIwIiwgIA0KICAgICAgICAgICAgZnJhbWUgPSBGQUxTRSwgaW5uZXIubWFyZ2lucyA9IGMoMC4xLDAuMSwwLjEsMC4xNSkpDQpgYGANCg0KDQoNCkZpbmFsbHksIHNhdmUgeW91ciBtYXAgYXMgYW4gUiBvYmplY3QgYW5kIGV4cG9ydCBpdC4NCg0KYGBge3J9DQphdmVyYWdlX2Nvc3QgPC0gdG1fc2hhcGUobXNvYV9sZmIpICsgDQogIHRtX3BvbHlnb25zKGNvbCA9ICJjb3N0X3Blcl9pbmNpZGVudCIsICBicmVha3MgPSBzZXEoMCwxMjAwLDMwMCksIHRpdGxlID0gIkNvc3QgcGVyIEluY2lkZW50ICjCoykiLA0KICAgICAgICAgICAgICBsYWJlbHMgPSBjKCIgIDAgLSAzMDAiLCAiPjMwMCAtIDYwMCIsICI+NjAwIC0gOTAwIiwgIj45MDAgLSAxMjAwIiksIHBhbGV0dGUgPSAiQmx1ZXMiLCBjb250cmFzdCA9IDEpICsgDQogIHRtX3NoYXBlKGxvbmRvbl9ib3VuZGFyeSkgKyB0bV9ib3JkZXJzKGNvbCA9ICJibGFjayIsIGx3ZCA9IDIpICsNCiAgdG1fc2NhbGVfYmFyKHBvc2l0aW9uID0gYygwLDApKSArDQogICB0bV9sYXlvdXQodGl0bGUgPSAiQXZlcmFnZSBjb3N0IG9mIGFuaW1hbCByZWxhdGVkIGluY2lkZW50cyBiZXR3ZWVuIDIwMDkgYW5kIDIwMjAiLCAgDQogICAgICAgICAgICBmcmFtZSA9IEZBTFNFLCBpbm5lci5tYXJnaW5zID0gYygwLjEsMC4xLDAuMSwwLjE1KSkNCmBgYA0KYGBge3IsIGV2YWw9RkFMU0V9DQp0bWFwX3NhdmUoYXZlcmFnZV9jb3N0LCAib3V0cHV0L21hcHMvYXZlcmFnZV9jb3N0X21zb2EucG5nIiwgd2lkdGggPSA4LCBoZWlnaHQgPSA1KQ0KYGBgDQoNCllvdSBjYW4gYWxzbyB2aWV3IHlvdXIgY2hvcm9wbGV0aCBhcyBhbiBpbnRlcmFjdGl2ZSBtYXAuIEl0IGhlbHBzIHRvIGFkZCBhbiBgYWxwaGFgIGFyZ3VtZW50IHRvIGNoYW5nZSB5b3VyIG1hcCdzIHRyYW5zcGFyZW5jeS4NCg0KYGBge3J9DQp0bWFwX21vZGUoInZpZXciKQ0KdG1fc2hhcGUobXNvYV9sZmIpICsgDQogIHRtX3BvbHlnb25zKGNvbCA9ICJjb3N0X3Blcl9pbmNpZGVudCIsIGJyZWFrcyA9IHNlcSgwLDEyMDAsMzAwKSwgDQogICAgICAgICAgICAgICBsYWJlbHMgPSBjKCIgIDAgLSAzMDAiLCAiPjMwMCAtIDYwMCIsICI+NjAwIC0gOTAwIiwgIj45MDAgLSAxMjAwIiksDQogICAgICAgICAgICAgIHRpdGxlID0gIkNvc3QgcGVyIEluY2lkZW50ICjCoykiLCBwYWxldHRlID0gIkJsdWVzIiwgY29udHJhc3QgPSAxLCBhbHBoYSA9IDAuOCkgKyANCiAgdG1fc2hhcGUobG9uZG9uX2JvdW5kYXJ5KSArIHRtX2JvcmRlcnMoY29sID0gImJsYWNrIiwgbHdkID0gMikgDQpgYGANCg0KIyBSZWNvbW1lbmRlZCByZXNvdXJjZXMNCg0KW0dlb2NvbXB1dGF0aW9uIHdpdGggUl0oaHR0cHM6Ly9nZW9jb21wci5yb2JpbmxvdmVsYWNlLm5ldC9pbmRleC5odG1sKSAgDQoNCltTaW1wbGUgRmVhdHVyZXMgZm9yIFJdKGh0dHBzOi8vci1zcGF0aWFsLmdpdGh1Yi5pby9zZi9pbmRleC5odG1sKSAgDQoNCltTcGF0aWFsIERhdGEgU2NpZW5jZSB3aXRoIFJdKGh0dHBzOi8vd3d3LnJzcGF0aWFsLm9yZy8pICANCg0KW0NyZWF0aW5nIGRlbW9ncmFwaGljIG1hcHMgaW4gUiB3aXRoIHRtYXAgcGFja2FnZXNdKGh0dHA6Ly93d3cuemV2cm9zcy5jb20vYmxvZy8yMDE4LzEwLzAyL2NyZWF0aW5nLWJlYXV0aWZ1bC1kZW1vZ3JhcGhpYy1tYXBzLWluLXItd2l0aC10aGUtdGlkeWNlbnN1cy1hbmQtdG1hcC1wYWNrYWdlcy8pDQo=